/*  This program is free software; you can redistribute it and/or modify it
    under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation; either version 2.1 of the License, or (at
    your option) any later version.
    For more details, see the GNU Lesser General Public License (www.fsf.org
    or the COPYING file somewhere in the package)
 */

#include "LifeCycle.hh"

/*! @file src/OSGi/LifeCycle.cc
    @brief Class OSGi::LifeCycle (non-inline) methods.
    @author @ref Guillaume_Terrissol
    @date 9th February 2009 - 22nd January 2014
    @note This file is distributed under the LGPL license.
    Refer to the file COPYING (or http://www.fsf.org) for more information.
 */

#include <cassert>
#include <map>
#include <set>

#include "Exception.hh"

namespace OSGi
{
//------------------------------------------------------------------------------
//                            Everybody's Changing...
//------------------------------------------------------------------------------

    /*! A default constructor is required
        (see LifeCycle::Private::mChanges).
     */
    LifeCycle::Change::Change()
        : mToState{}
        , mRecursion{ERecurse::eNo}
    { }


    /*! @param pToState   Target state
        @param pRecursion How to recurse the change to children bundles
     */
    LifeCycle::Change::Change(std::string pToState, ERecurse pRecursion)
        : mToState{pToState}
        , mRecursion{pRecursion}
    { }


    /*! Defaulted.
     */
    LifeCycle::Change::~Change() = default;


    /*! @param pBundle  Bundle for which try to change state
        @param pContext Current execution context
        @retval true  If @p pBundle state was changed as expected
        @retval false If the change failed 
     */
    bool LifeCycle::Change::operator()(Bundle* pBundle, ContextPtr pContext) const
    {
        return call(pBundle, pContext);
    }


    /*! @return The target state
     */
    std::string LifeCycle::Change::toState() const
    {
        return mToState;
    }


    /*! @return How the bundle change its children states
     */
    LifeCycle::ERecurse LifeCycle::Change::doesRecurse() const
    {
        return mRecursion;
    }


    /*! @fn bool LifeCycle::Change::call(Bundle* pBundle, ContextPtr pContext) const
        @copydetails LifeCycle::Change::operator()(Bundle*, ContextPtr) const
     */


//------------------------------------------------------------------------------
//
//------------------------------------------------------------------------------

    /// @cond DEVELOPMENT

    /*! @brief LifeCycle private implementation.
        @version 0.5
     */
    class LifeCycle::Private
    {
    public:

            Private() = default;                                                    //!< Default constructor.

        //       From                  change name  
        std::map<std::string, std::map<std::string, Change::Ptr>>   mChanges;       //!< Set of changes.
        std::set<std::string>                                       mStates;        //!< Set of states.
        std::string                                                 mInitialState;  //!< Initial state.
    };

    /// @endcond

//------------------------------------------------------------------------------
//
//------------------------------------------------------------------------------

    /*! Defaulted.
     */
    LifeCycle::LifeCycle() = default;


    /*! Defaulted.
     */
    LifeCycle::~LifeCycle() = default;


    /*! @param pName Name of the state to set as the life cycle entry point
     */
    void LifeCycle::declareInitialState(std::string pName)
    {
        declareState(pName);
        pthis->mInitialState = pName;
    }


    /*! @param pName New state name
     */
    void LifeCycle::declareState(std::string pName)
    {
        if (pthis->mStates.count(pName) != 0)
        {
            throw LogicError{"State " + pName + " already defined"};
        }
        pthis->mStates.insert(pName);
        if (pthis->mInitialState.empty())
        {
            pthis->mInitialState = pName;
        }
    }


    /*! @param pName   Action name associated to the change
        @param pFrom   State from which to change
        @param pTo     Target state
        @param pChange The change itself
     */
    void LifeCycle::declareChange(std::string pName,
                                  std::string pFrom,
                                  std::string pTo,
                                  Change::Ptr pChange)
    {
        if (pthis->mStates.count(pFrom) == 0)
        {
            throw LogicError{"State " + pFrom + " (from) unknown"};
        }
        if (pthis->mStates.count(pTo) == 0)
        {
            throw LogicError{"State " + pTo + " (to) unknown"};
        }
        auto&   lChanges    = pthis->mChanges[pFrom];
        if (lChanges.count(pName) != 0)
        {
            throw LogicError{"Change " + pName + " already defined from state " + pFrom};
        }
        lChanges[pName] = pChange;
    }


    /*! @return The initial state name
     */
    std::string LifeCycle::initialState() const
    {
        return pthis->mInitialState;
    }


    /*! @param pName Action name associated to the change
        @param pFrom From which state to change
        @note The change called @p pName, declared from state @p pFrom
     */
    LifeCycle::Change::Ptr LifeCycle::change(std::string pName, std::string pFrom) const
    {
        auto    lChanges    = pthis->mChanges.find(pFrom);
        if (lChanges != end(pthis->mChanges))
        {
            auto    lChange = lChanges->second.find(pName);
            if (lChange != end(lChanges->second))
            {
                return lChange->second;
            }
        }

        return {};
    }

//------------------------------------------------------------------------------
//                          Standard Actions and States
//------------------------------------------------------------------------------

    const std::string   kResolve        = "resolve";
    const std::string   kStart          = "start";
    const std::string   kStop           = "stop";
    const std::string   kUninstall      = "uninstall";

    const std::string   kInstalled      = "installed";
    const std::string   kUninstalled    = "uninstalled";
    const std::string   kResolved       = "resolved";
    const std::string   kStarting       = "starting";
    const std::string   kActive         = "active";
    const std::string   kStopping       = "stopping";


//------------------------------------------------------------------------------
//                           Additional Documentation
//------------------------------------------------------------------------------

    /*! @class LifeCycle
        @section OSGi_LifeCycle_Graph Bundle life cycle
        The following chart represents the complete life cycle of a Bundle,
        driven by @ref OSGi_Bundle_Changes_Group "actions" through its different
        @ref OSGi_Bundle_States_Group "states ".
        @dot
        digraph bundle_loading
        {
            bgcolor=transparent;
            edge [fontname="Courier New",fontsize=8, charset="utf-8"];
            node [fontname="Courier New",fontsize=10, charset="utf-8"];

            start       [shape=point,                  color=black,   width=0.2]
            installed   [shape=rect,  style="rounded", color=blue,    fontcolor=blue,      label="Installed"]
            resolved    [shape=rect,  style="rounded", color=blue,    fontcolor=blue,      label="Resolved"]
            uninstalled [shape=rect,  style="rounded", color=red,     fontcolor=red,       label="Uninstalled"]
            starting    [shape=rect,  style="rounded", color=blue,    fontcolor=blue,      label="Starting"]
            active      [shape=rect,  style="rounded", color=green,   fontcolor=green,     label="Active"]
            stopping    [shape=rect,  style="rounded", color=orange,  fontcolor=orange,    label="Stopping"]

                start       -> installed    [color=black,   fontcolor=black,    label="Install"]
                installed   -> uninstalled  [color=red,     fontcolor=red,      label="Uninstall"]
                installed   -> resolved     [color=blue,    fontcolor=blue,     label="Resolve"]
                resolved    -> uninstalled  [color=orange,  fontcolor=orange,   label="Uninstall"]

                resolved    -> starting     [color=blue,    fontcolor=blue,     label="Start"]
                starting    -> active       [color=green,   fontcolor=green,                style=dashed]
                active      -> stopping     [color=orange,  fontcolor=orange,   label="Stop"]
                stopping    -> resolved     [color=orange,                                  style=dashed]

                { rank = same; uninstalled; resolved; }
                { rank = same; starting; stopping; }
        }
        @enddot
     */

    /*! @var LifeCycle::ERecurse LifeCycle::eNo
        Use this value while calling LifeCycle::declareChange() to
        indicate that the children bundles shall keep their current state.
     */

    /*! @var LifeCycle::ERecurse LifeCycle::eThisFirst
        Use this value while calling LifeCycle::declareChange() to
        indicate that a bundle shall change its state before its clidren
     */

    /*! @var LifeCycle::ERecurse LifeCycle::eThisLast
        Use this value while calling LifeCycle::declareChange() to
        indicate that a bundle shall change its children state before its own
     */
}
